package com.ym.core.interceptor;

import cn.hutool.core.codec.Base64;
import cn.hutool.crypto.CryptoException;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.SignAlgorithm;
import com.alibaba.fastjson.JSONObject;
import com.ym.common.constant.Coder;
import com.ym.common.constant.Constants;
import com.ym.common.constant.UserDetails;
import com.ym.common.utils.ServletUtil;
import com.ym.core.annotation.ApiSign;
import com.ym.core.domain.APIResponse;
import com.ym.core.shiro.ShiroUtil;
import com.ym.medical.dao.SysConfigDao;
import com.ym.medical.domain.entity.SysConfigEntity;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeUnit;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * Author: zhangfengzhou
 * Date: 2019-04-03
 * Time: 10:11
 * 接口签名拦截器
 */
@Component
public class SignInterceptor extends HandlerInterceptorAdapter {

	private static final Logger LOGGE = LoggerFactory.getLogger(SignInterceptor.class);

	@Autowired
	private SysConfigDao sysConfigDao;

	/**
	 * 签名开关
	 */
	@Value("${config-center.sign.enabled}")
	private boolean enabled;

	/**
	 * 签名超时时间
	 */
	@Value("${config-center.sign.timeout}")
	private long timeout;

	/**
	 * 默认签名
	 */
	@Value("${config-center.sign.defaultSign}")
	private String defaultSign;

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		// 验签开关
		if (!enabled) {
			return true;
		}

		if (!(handler instanceof HandlerMethod)) {
			return true;
		}

		// 签名注解判断
		HandlerMethod handlerMethod = (HandlerMethod) handler;
		ApiSign classApiSign = handlerMethod.getBeanType().getAnnotation(ApiSign.class);
		ApiSign methodApiSign = handlerMethod.getMethodAnnotation(ApiSign.class);
		if (methodApiSign != null && !methodApiSign.isSign()) {
			//方法签名为false
			return true;
		} else if (methodApiSign == null && (classApiSign == null || !classApiSign.isSign())) {
			//方法无签名，且类无签名或类签名为false
			return true;
		}

		// 未登录, 则用默认签名校验
		String appKey = null;
		UserDetails loginUser = ShiroUtil.getLoginUser(true);
		if (loginUser == null) {
			SysConfigEntity sysConfigEntity = sysConfigDao.selectOne(SysConfigEntity.builder().theKey(defaultSign).build());
			JSONObject params = JSONObject.parseObject(sysConfigEntity.getTheValue());
			appKey = params.getString(Constants.SignConstant.APP_KEY);
		} else {
			appKey = loginUser.getAppKey();
		}
		String timestampStr = request.getHeader(Constants.SignConstant.TIMESTAMP);
		String sign = request.getHeader(Constants.SignConstant.SIGN);

		// 校验参数完整性
		if (StringUtils.isBlank(timestampStr) || StringUtils.isBlank(sign)) {
			LOGGE.debug("请求参数不完整[appKey:{}][timestamp:{}][sign:{}]", appKey, timestampStr, sign);
			ServletUtil.write(response, APIResponse.fail(Coder.System.SIGNATURE_NO_DATA), MediaType.APPLICATION_JSON_UTF8_VALUE);
			return false;
		}

		// 校验接口调用是否超时
		long timestamp = Long.valueOf(timestampStr);
		long currTimestamp = System.currentTimeMillis();
		if (currTimestamp - timestamp > TimeUnit.MINUTES.toMillis(timeout)) {
			LOGGE.debug("接口调用超时");
			ServletUtil.write(response, APIResponse.fail(Coder.System.SIGNATURE_TIME_OUT), MediaType.APPLICATION_JSON_UTF8_VALUE);
			return false;
		}

		// 校验签名,Body有数据校验Body，否则校验Url参数
		String requestData = ServletUtil.getBodyToParamString(request);
		if (StringUtils.isBlank(requestData) || "{}".equals(requestData) || requestData.trim().length() == 0) {
			//请求体没有报文
			LOGGE.debug("body无内容,验证url内容");
			requestData = ServletUtil.getParamString(request);
		}

		// 验证签名
		boolean verify = false;
		try {
			verify = SecureUtil.sign(SignAlgorithm.MD5withRSA, null, appKey).verify(requestData.getBytes(), Base64.decode(sign));
		} catch (CryptoException e) {
			verify = false;
		}
		if (!verify) {
			LOGGE.debug("用户签名错误[签名原文：{}]", requestData);
			ServletUtil.write(response, APIResponse.fail(Coder.System.SIGNATURE_ERROR), MediaType.APPLICATION_JSON_UTF8_VALUE);
			return false;
		}
		return true;
	}
}
